import { getCurrentInstance, ComponentInternalInstance } from 'vue';
interface AdaptiveLayoutResult {
	num : number;
	height : number;
	debug : {
		baseNum : number;
		safeNum : number;
		actualWidth : number;
		aspectRatio : number;
	}
}
interface Node {
	zIndex ?: number;
	childNode ?: Node;
	[key : string] : any;
}
// 规则检验
import testDefaultMethod from './test';
// 重新导出默认方法
const test = testDefaultMethod;
import layerDefaultMethod from './layer';
const layer = layerDefaultMethod;

// 导入蓝牙模块 api
import {
	initBluetooth
} from '@/common/comFun/bluetooth'

// 初始化蓝牙管理器
// const $bluetooth = initBluetooth()



import pageScanPopupFun from './pageScanPopupFun';
const $scanPopup = pageScanPopupFun;


const PLATFORM = process.env.UNI_PLATFORM;
// 统一的资源域名前缀（建议配置成 CDN 域名）
const SERVER_RESOURCE_BASE_URL = "https://api.hzyc.org.cn"


// 下拉选方法接口定义

export const Utils = {
	test,
	layer,
	$scanPopup,
	initBluetooth,
	// $bluetooth,
	/**
	 * 替换调度相关页面的路径
	 * 用于将旧路径前缀映射到新的路径前缀，避免在项目中大量修改路径。
	 *
	 * 使用场景：
	 * - 页面跳转时需要统一转换路径
	 * - 全局路径映射（固定规则，不随用户变化）
	 *
	 * 规则说明：
	 * - 如果路径以 "/st_ticket/DispatchList" 开头，则替换为 "/st_ticket/page_dispatch"
	 * - 其余路径保持不变
	 *
	 * 示例：
	 * replaceDispatchPath("/st_ticket/DispatchList/Detail?id=1")
	 * => "/st_ticket/page_dispatch/Detail?id=1"
	 */
	replaceDispatchPath(path : string) : string {
		// 固定映射规则（以后要扩展就在这里加）
		const pathMappings : Record<string, string> = {
			"/st_ticket/DispatchList/DispatchList": "/st_ticket/page_dispatch/dispatch-list",
			"/st_ticket/DispatchList": "/st_ticket/page_dispatch",
			"/st_ticket/CreateTicket": "/st_ticket/page_ticket/ticket-creat",
			"/st_ticket/TicketList": "/st_ticket/page_ticket/ticket-list",
			"/pages/mine": "/st_pages/mine",
			"/st_ticket/WorkList": "/st_ticket/page_work/work-list",
			"/st_ticket/CreateWorks": "/st_ticket/page_work/work-create",
			"/st_ticket/StatsKanban": "/st_ticket/page_kanban/page_kanban",
			"/st_qms/spc/index": "/st_qms/page_spc/index",
			"/st_qms/LabReport/experimentResult": "/st_qms/page_labreport/page_labreport_list",
		}

		// 遍历规则，找到匹配的前缀
		for (const prefix in pathMappings) {
			if (path.startsWith(prefix)) {
				// 命中规则：用目标前缀替换掉原始前缀
				return pathMappings[prefix] + path.slice(prefix.length)
			}
		}

		// 没有匹配任何规则，返回原始路径
		return path
	},

	/**
	 * 资源路径管理工具
	 *
	 * 功能：
	 * - 根据当前运行平台（App/H5/微信小程序等），自动返回正确的资源路径
	 * - 避免微信小程序打包超过 200KB 的本地资源
	 *
	 * 使用方式：
	 *   <image :src="getResourceUrl('images/bg.jpg')" />
	 *
	 * 规则：
	 * - App/H5：优先走本地 /static/ 目录下的资源
	 * - 微信小程序（mp-weixin）：统一走服务器资源，避免包体超限
	 */
	getResourceUrl(relativePath : string) {
		if (PLATFORM === 'mp-weixin') {
			// ✅ 微信小程序端：必须走网络资源，避免本地 static 被打包
			// #ifdef MP-WEIXIN
			return SERVER_RESOURCE_BASE_URL + "/assets/static/" + relativePath
			// #endif
		} else {
			// #ifndef MP-WEIXIN
			// ✅ App/H5 等平台：直接走本地 static 目录
			return '/static/' + relativePath
			// #endif
		}
	},
	/** 千分位转化
	 * @param {String | Number} price 需要转化的数组
	 * @param {Number} 保留的小数位数，默认2
	 * @return {String} 返回千分位字符串
	 */
	formatPrice(price : string | number = 0, decimalPlaces : number = 2) : string {
		// 将价格转换为字符串（如果它还不是）  
		price = String(price);

		// 分离整数部分和小数部分  
		const parts = price.split('.');
		const integerPart = parts[0];
		const decimalPart = parts[1] ? parts[1].slice(0, decimalPlaces) : '';

		// 格式化整数部分，添加千分位逗号  
		const formattedIntegerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');

		// 拼接整数部分和小数部分（如果需要的话）  
		const formattedPrice = decimalPart ? `${formattedIntegerPart}.${decimalPart}` : formattedIntegerPart;

		return formattedPrice;
	},
	/** 查数组中是否存在某一时间后的数据
	 * @param {Object[]} array
	 * @param {Date} targetDate
	 * @param {string} text 时间字段名，默认'time'
	 */
	binarySearch(array : object[], targetDate : Date, text : string = 'time') : boolean {
		let left = 0;
		let right = array.length - 1;

		while (left <= right) {
			const mid = Math.floor((left + right) / 2);
			// @ts-ignore
			const midDate = new Date(array[mid][text]);

			if (midDate > targetDate) {
				return true;
			} else if (midDate < targetDate) {
				left = mid + 1;
			} else {
				// 如果需要严格大于，即使相等也要继续搜索  
				left = mid + 1;
			}
		}

		return false;
	},
	// 安全日期处理
	safeDateParse(dateStr : string) : number {
		if (!dateStr) return Date.now();
		try {
			const date = new Date(dateStr);
			return isNaN(date.getTime()) ? Date.now() : date.getTime();
		} catch {
			return Date.now();
		}
	},
	/** 获取某年某月的天数
	 * @param {number} year
	 * @param {number} month
	 */
	getDaysInMonth(year : number, month : number) : number {
		// JavaScript中月份是从0开始的，即0代表1月，1代表2月，依此类推  
		// 因此，我们需要在输入的月份上减1  
		const date = new Date(year, month - 1, 1); // 创建指定年月的第一天  

		// 设置为下一个月的第一天，但日期设为0，这将导致Date对象自动回退到上个月的最后一天  
		date.setMonth(date.getMonth() + 1, 0);

		// 返回上个月的最后一天，即是我们想要的这个月的天数  
		return date.getDate();
	},
	/**  
	 * 显示一个不带图标的消息提示框。  
	 *  
	 * @param {any} content - 要显示的消息内容。  
	 * @param {number} [time=3000] - 消息显示的持续时间，单位为毫秒，默认为 3000 毫秒。  
	 */
	msg(content : any, time : number = 3000) : void {
		// 参数验证  
		if (typeof content !== 'string' || typeof time !== 'number' || time <= 0) {
			console.error('Invalid parameters for msg function.');
			return;
		}

		try {
			// 显示消息提示框  
			uni.showToast({
				icon: 'none',
				title: content,
				duration: time
			});
		} catch (error) {
			// 错误处理  
			console.error('An error occurred while showing the toast:', error);
		}
	},
	/**showLoading 显示加载动画
	 * 需要搭配 uni.hideLoading(); 使用
	 * @param {string} [content="加载数据中..."] 加载动画的文字
	 * @param {boolean} [mask=true] 是否显示透明蒙层，防止触摸穿透，默认：false
	 */
	showLoading(content : string = "加载数据中...", mask : boolean = true) : void {
		uni.showLoading({
			title: content,
			mask: mask
		});
	},
	/**hideLoading 关闭加载动画
	 * @param {number} [timer=0] 关机加载动画时长
	 */
	hideLoading(timer : number = 0) : void {
		if (timer > 0) {
			const t = setTimeout(function () {
				uni.hideLoading();
				clearTimeout(t);
			}, timer);
		} else {
			uni.hideLoading();
		}
	},
	/**
	 * @description 进行延时，以达到可以简写代码的目的 比如: await uni.$utils.sleep(20)将会阻塞20ms
	 * @param {number} value 堵塞时间 单位ms 毫秒
	 * @returns {Promise} 返回promise
	 */
	sleep(value : number = 30) {
		return new Promise((resolve : any) => {
			setTimeout(() => {
				resolve()
			}, value)
		})
	},
	/**
	 * 判断页面栈中是否包含某路径
	 * @param {string} targetPath 查询路径名称，支持带参数或不带参数的路径
	 * @returns {boolean} 是否包含目标路径
	 */
	isPageInStack(targetPath : string) : boolean {
		// 获取当前页面栈，uniapp官方API
		const pages = getCurrentPages();

		// 页面栈为空时直接返回false
		if (!pages || pages.length === 0) return false;

		/**
		 * 统一处理路径格式
		 * 1. 移除首尾斜杠
		 * 2. 去除查询参数部分
		 * 3. 转换为小写以兼容不同大小写的路径写法
		 */
		const normalizePath = (path : string) : string => {
			if (typeof path !== 'string') return '';
			return path
				.replace(/^\/|\/$/g, '') // 移除首尾斜杠
				.split('?')[0] // 去除查询参数
				.toLowerCase(); // 统一转为小写
		};

		// 标准化目标路径
		const normalizedTarget = normalizePath(targetPath);

		// 遍历页面栈查找匹配的路径
		return pages.some(page => {
			// 兼容不同平台和uniapp版本的路径获取方式
			let pagePath : string | undefined;

			if (typeof page.route === 'string') {
				// 优先使用route属性（最可靠的方式）
				pagePath = page.route;
			} else if (typeof (page as any).__route__ === 'string') {
				// 兼容旧版本的__route__属性
				pagePath = (page as any).__route__;
			} else {
				// 针对Vue3版本，尝试从$vm获取页面路径（通过类型断言绕过TS检查）
				const vueInstance = (page as any).$vm;
				if (vueInstance && typeof vueInstance.$page?.fullPath === 'string') {
					pagePath = vueInstance.$page.fullPath;
				}
			}

			// 标准化页面路径并比较
			return pagePath ? normalizePath(pagePath) === normalizedTarget : false;
		});
	},
	/**
	 * 检查当前路由栈中是否包含指定路径的页面，并返回相关信息
	 * @param path 要检查的页面路径（例如：'/pages/home/index'）
	 * @returns 包含exists和index的对象
	 */
	findPageInStack(path : string) : { exists : boolean; index : number } {
		// 获取当前路由栈实例数组
		const pages = getCurrentPages();

		if (!pages || pages.length === 0) {
			return { exists: false, index: -1 };
		}

		// 遍历路由栈，查找目标路径
		for (let i = 0; i < pages.length; i++) {
			const page = pages[i];
			// 不同平台下的路径可能在 page.$page.fullPath 或 page.route 中
			const pagePath = page.$page?.fullPath || page.route;

			// 处理可能的查询参数，只比较路径部分
			const purePath = pagePath?.split('?')[0];

			if (purePath === path) {
				return { exists: true, index: i };
			}
		}

		// 未找到目标路径
		return { exists: false, index: -1 };
	},
	/**
	 * 判断文件名称对应的文件类型
	 * @param filename - 文件名称（包含扩展名）
	 * @returns 文件类型，可能的值：'image'（图片）、'video'（视频）、'text'（文本）、'pdf'（PDF文档）、'spreadsheet'（电子表格/演示文档）、'unknown'（未知类型）
	 */
	isFileType(filename : string) : 'image' | 'video' | 'text' | 'pdf' | 'spreadsheet' | 'unknown' {
		if (Utils.isEmpty(filename)) return 'unknown';
		// 查找最后一个点号的位置
		const lastDot = String(filename).lastIndexOf('.');
		if (lastDot === -1 || lastDot === String(filename).length - 1) {
			return 'unknown'; // 没有扩展名或点号在末尾
		}
		const extension = String(filename).slice(lastDot + 1).toLowerCase();

		// 定义不同文件类型的扩展名
		// 图片
		const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tiff', 'jfif', 'dpg', 'tif', 'image'];
		// 视频
		const videoExtensions = ['mp4', 'mov', 'avi', 'wmv', 'mkv', 'flv', 'webm', 'mpeg', 'mpg'];
		// 文件类型
		const textExtensions = ['txt', 'md', 'csv', 'json', 'xml', 'html', 'htm', 'css', 'js'];
		const pdfExtensions = ['pdf'];
		const spreadsheetExtensions = ['xls', 'xlsx', 'csv', 'ods', 'tsv', 'ppt', 'pptx'];

		// 检查文件扩展名属于哪个类别
		if (imageExtensions.includes(extension)) {
			return 'image';
		} else if (videoExtensions.includes(extension)) {
			return 'video';
		} else if (textExtensions.includes(extension)) {
			return 'text';
		} else if (pdfExtensions.includes(extension)) {
			return 'pdf';
		} else if (spreadsheetExtensions.includes(extension)) {
			return 'spreadsheet';
		} else {
			return 'unknown'; // 不属于上述任何类别
		}
	},
	/**
	 * 计算自适应布局参数
	 * 根据容器宽度和间距，动态计算适合的列数和元素高度
	 * @param containerWidth - 容器宽度（像素）
	 * @param gap - 元素之间的间距（像素）
	 * @returns 自适应布局结果，包含列数、元素高度和调试信息
	 */
	calculateAdaptiveLayout(containerWidth : number, gap : number) : AdaptiveLayoutResult {
		// 设计稿基准配置（按容器宽度降序排列）
		const breakpoints = [{
			width: 1980,
			num: 8,
			height: 200
		},
		{
			width: 853,
			num: 3,
			height: 100
		}
		];

		// 边界值处理
		if (containerWidth >= breakpoints[0].width) {
			return {
				num: breakpoints[0].num,
				height: breakpoints[0].height,
				debug: {
					baseNum: breakpoints[0].num,
					safeNum: breakpoints[0].num,
					actualWidth: containerWidth / breakpoints[0].num,
					aspectRatio: breakpoints[0].height / (containerWidth / breakpoints[0].num)
				}
			};
		}
		if (containerWidth <= breakpoints[1].width) {
			return {
				num: breakpoints[1].num,
				height: breakpoints[1].height,
				debug: {
					baseNum: breakpoints[1].num,
					safeNum: breakpoints[1].num,
					actualWidth: containerWidth / breakpoints[1].num,
					aspectRatio: breakpoints[1].height / (containerWidth / breakpoints[1].num)
				}
			};
		}

		// 线性插值计算基础数量和高度
		const [min, max] = breakpoints;
		const ratio = (containerWidth - min.width) / (max.width - min.width);
		let baseNum = Math.round(min.num + (max.num - min.num) * ratio);
		baseNum = Math.max(1, baseNum);
		const baseHeight = Math.round(min.height + (max.height - min.height) * ratio);

		// 二分法计算安全显示数量（考虑间距）
		let safeNum = baseNum;
		let low = 1;
		let high = baseNum;

		while (low <= high) {
			const mid = Math.floor((low + high) / 2);
			const testWidth = (containerWidth - gap * (mid - 1)) / mid;
			if (testWidth > 0) {
				safeNum = mid;
				low = mid + 1;
			} else {
				high = mid - 1;
			}
		}

		// 确定最终显示数量
		const finalNum = Math.min(baseNum, safeNum);

		// 计算实际盒子尺寸（保持原始宽高比）
		const actualWidth = (containerWidth - gap * (finalNum - 1)) / finalNum;
		const aspectRatio = baseHeight / (containerWidth / baseNum);
		const finalHeight = Math.round(actualWidth * aspectRatio);

		return {
			num: finalNum,
			height: finalHeight,
			debug: {
				baseNum,
				safeNum,
				actualWidth,
				aspectRatio
			}
		};
	},
	/** 处理递归对象层级（返回新对象，避免引用污染）
	 * @param {Object} node 递归对象
	 * @param {number} [level=1] 层级
	 * @returns 新的层级化对象
	 */
	addZIndex(node : Node, level : number = 1) : Node {
		console.log("当前节点:", node);

		// 创建新对象，复制原属性并添加zIndex
		const newNode : Node = {
			...node,
			zIndex: level,
		};

		// 递归处理子节点
		if (!Utils.isEmpty(newNode.childNode)) {
			if (Array.isArray(newNode.childNode)) {
				// 如果是数组，逐个递归
				newNode.childNode = newNode.childNode.map((child : Node) =>
					Utils.addZIndex(child, level + 1)
				);
			} else if (typeof newNode.childNode === "object") {
				// 如果是单个对象
				newNode.childNode = Utils.addZIndex(newNode.childNode, level + 1);
			}
		}

		return newNode;
	},
	/**
	 * 根据zIndex值在节点树中查找匹配的节点
	 * 采用递归方式遍历节点及其子节点
	 * @param node - 起始节点，从该节点开始查找
	 * @param targetZIndex - 要查找的目标zIndex值
	 * @returns 找到的匹配节点，如果未找到则返回null
	 */
	findNodeByZIndex(node : Node, targetZIndex : number) : Node | null {
		// 检查当前节点的 zIndex 是否匹配
		if (node.zIndex === targetZIndex) {
			return node; // 返回匹配节点
		}

		// 如果当前节点有 childNode，则递归处理它
		if (node.childNode && typeof node.childNode === 'object') {
			const childNodeResult = Utils.findNodeByZIndex(node.childNode, targetZIndex);
			if (childNodeResult) {
				return childNodeResult; // 返回子节点中匹配的结果
			}
		}

		// 如果没有找到匹配节点，则返回 null
		return null;
	},
	/** 节流函数（带this类型注释）
	 * @param {Function} fn 自定义方法
	 * @param {number} delay 节流时长，默认3000ms
	 * @returns 节流后的函数
	 */
	throttle<T extends (...args : any[]) => any>(
		fn : T,
		delay : number = 3000
	) : (...args : Parameters<T>) => void {
		let lastCallCom = 0;

		// 为返回的函数指定this类型为调用者的类型（泛型推断）
		return function (this : ThisParameterType<T>, ...args : Parameters<T>) {
			const now = Date.now();
			if (now - lastCallCom < delay) {
				return;
			}
			lastCallCom = now;
			// 应用正确的this上下文和参数类型
			fn.apply(this, args);
		};
	},
	/**  
	 * 创建一个节流后的复制函数  
	 * @param {string} textToCopy 复制到剪切板的内容  
	 * @param {number} delay 节流时间间隔，默认为3000毫秒  
	 * @returns {void} 
	 */
	TextCopyFun(textToCopy : string, delay : number = 3000) : void {
		if (Utils.isEmpty(textToCopy)) {
			return;
		}


		// 创建节流后的复制函数  
		const throttledCopyText = Utils.throttle(function () {
			// 设置系统剪贴板的内容  
			uni.setClipboardData({
				data: textToCopy,
				success: () => {
					uni.showToast({
						title: '已复制',
						icon: 'success',
						duration: 2000
					});
				},
				fail: (err) => {
					uni.showToast({
						title: '复制失败',
						icon: 'none',
						duration: 2000
					});
				}
			});
		}, delay);

		// 执行节流后的函数  
		throttledCopyText();
	},
	/**
	 * 精确判断数据的类型
	 * 基于Object.prototype.toString方法实现，可准确识别多种内置数据类型
	 * @param data - 需要判断类型的数据
	 * @param type - 目标类型字符串（首字母大写，如"String"、"Number"、"Array"等）
	 * @returns 如果数据类型与目标类型一致则返回true，否则返回false
	 */
	isDataType(data : any, type : string) : boolean {
		// Object.prototype.toString.call(data)会返回形如"[object Type]"的字符串
		// 例如：[object String]、[object Number]、[object Array]等
		// 将其与"[object 目标类型]"进行比对，实现精确的类型判断
		return Object.prototype.toString.call(data) === '[object ' + type + ']';
	},
	/**  
	 * ltrim - 去除字符串左侧的指定字符  
	 *  
	 * @param str 要处理的字符串  
	 * @param char 要从左侧去除的字符  
	 * @returns 返回去除指定字符后的新字符串  
	 *  
	 * 注意事项：  
	 * - 如果 char 不在 str 中，将返回整个 str  
	 * - 如果 char 在 str 的开始位置出现，将返回从第一个非 char 字符开始到结尾的子字符串  
	 * - 此函数不会处理 str 中多个连续的 char，只会处理第一个出现的 char  
	 */
	ltrim(str : string, char : string) : string {
		let pos = str.indexOf(char);
		let sonstr = str.substr(pos + 1);
		return sonstr;
	},
	/**  
	 * rtrim - 从字符串右侧（末尾）去除指定的字符。  
	 *  
	 * @param str 要处理的字符串。  
	 * @param char 要从右侧去除的字符。  
	 * @returns 返回去除指定字符后的新字符串。如果字符不在字符串中，则返回原字符串。  
	 */
	rtrim(str : string, char : string) : string {
		let pos = str.lastIndexOf(char);

		// 如果char不在str中，或者char不是str的最后一个字符，则返回原字符串  
		if (pos === -1 || str.charAt(pos) !== char) {
			return str;
		}

		// 如果char是str的最后一个字符或多个连续字符的最后一个，则返回去除它们的子字符串  
		return str.substr(0, pos);
	},
	/**
	 * 拼接URL参数（兼容小程序环境）
	 * @param baseUrl 基础URL
	 * @param params 参数对象
	 * @returns 拼接后的URL
	 */
	joinUrlParams(baseUrl : string, params : Record<string, string | number | boolean | undefined>) : string {
		const paramsArray : string[] = [];

		// 遍历参数并拼接
		Object.entries(params).forEach(([key, value]) => {
			if (value !== undefined && value !== null && value !== '') {
				paramsArray.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
			}
		});

		if (paramsArray.length === 0) {
			return baseUrl;
		}

		// 检查基础URL是否已有查询参数
		const separator = baseUrl.includes('?') ? '&' : '?';
		return `${baseUrl}${separator}${paramsArray.join('&')}`;
	},
	/**
	 * 打开webview网页
	 */
	navigateToWebView(params : {
		url : string;
		title ?: string;
		navBgColor ?: string;    // 如: "#007aff"
		navTextColor ?: string;  // 仅支持 "black" 或 "white"
	}) {
		// 构建参数对象
		const queryParams = {
			url: params.url,
			title: params.title,
			navBgColor: params.navBgColor,
			navTextColor: params.navTextColor
		};
		// 拼接页面路径和参数
		const targetUrl = Utils.joinUrlParams('/commonPage/Tool/web-view', queryParams);

		uni.navigateTo({
			url: targetUrl
		});
	},

	/**
	 * 保留当前页面，跳转到应用内的某个页面，使用uni.navigateBack可以返回到原页面。
	 */
	navigateTo(url : string, params ?: Record<string, any>) : void {
		uni.navigateTo({
			url: Utils.parseUrl(url, params)
		})
	},
	/**
	 * 关闭当前页面，跳转到应用内的某个页面。
	 */
	redirectTo(url : string, params ?: Record<string, any>) : void {
		uni.redirectTo({
			url: Utils.parseUrl(url, params)
		});
	},
	/**
	 * 关闭所有页面，打开到应用内的某个页面。
	 */
	reLaunch(url : string, params ?: Record<string, any>) : void {
		uni.reLaunch({
			url: Utils.parseUrl(url, params)
		});
	},
	/**
	 * 跳转到 tabBar 页面，并关闭其他所有非 tabBar 页面。
	 */
	switchTab(url : string, params ?: Record<string, any>) : void {
		uni.switchTab({
			url: Utils.parseUrl(url, params)
		});
	},
	/**
	 * 关闭当前页面，返回上一页面或多级页面
	 */
	navigateBack(delta : number) : void {
		uni.navigateBack({
			delta: delta
		});
	},
	/**
	 * 预加载页面，是一种性能优化技术。被预载的页面，在打开时速度更快。
	 */
	preloadPage(url : string, params ?: Record<string, any>) : void {
		uni.preloadPage({
			url: Utils.parseUrl(url, params)
		});
	},


	/**
	 * rpx转px
	 * @param number num
	 */
	rpx2px(num : number) : number {
		// const info = uni.getSystemInfoSync()
		// let scale = 750 / info.windowWidth;
		// return (Number.isNaN(parseFloat(num)) ? 0 : parseFloat(num)) / scale;
		return uni.upx2px(num);
	},
	/**
	 * @param number num
	 */
	px2rpx(num : number) : number {
		return num / (uni.upx2px(num) / num);
	},

	/**
	 * @description 运行期判断平台
	 * @returns {string} 返回所在平台(小写) 
	 * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
	 */
	os() {
		// #ifdef APP || H5 || MP-WEIXIN
		return uni.getDeviceInfo().platform.toLowerCase()
		// #endif
		// #ifndef APP || H5 || MP-WEIXIN
		return uni.getSystemInfoSync().platform.toLowerCase()
		// #endif
	},
	/**
	 * @description 获取系统信息同步接口
	 * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync 
	 */
	sys() {
		// #ifndef MP-WEIXIN
		return uni.getSystemInfoSync();
		// #endif

		// #ifdef MP-WEIXIN
		return {
			...uni.getSystemSetting(),
			...uni.getWindowInfo(),
			...uni.getDeviceInfo(),
			...uni.getAppAuthorizeSetting(),
			...uni.getAppBaseInfo()
		}
		// #endif
	},
	getWindowInfo() {
		let ret = {}
		// #ifdef APP || H5
		ret = uni.getWindowInfo()
		// #endif
		// #ifndef APP || H5
		ret = Utils.sys()
		// #endif
		return ret
	},
	getDeviceInfo() {
		let ret = {}
		// #ifdef APP || H5 || MP-WEIXIN
		ret = uni.getDeviceInfo()
		// #endif
		// #ifndef APP || H5 || MP-WEIXIN
		ret = Utils.sys()
		// #endif
		return ret
	},
	/**
	 * 获取当前设备窗口的宽度和高度
	 * 基于uni-app的API实现，适用于多端环境（小程序、H5、App等）
	 * @returns 包含窗口宽高的对象
	 *   - w: 窗口宽度（像素）
	 *   - h: 窗口高度（像素）
	 */
	getSystemInfo() : { w : number; h : number } {
		// 调用uni-app提供的同步方法获取系统信息
		// uni.getSystemInfoSync()返回设备和应用的各种系统信息，如屏幕尺寸、窗口尺寸等
		// #ifndef MP-WEIXIN
		const info = uni.getSystemInfoSync();
		// #endif

		// #ifdef MP-WEIXIN
		const info = {
			...uni.getSystemSetting(),
			...uni.getWindowInfo(),
			...uni.getDeviceInfo(),
			...uni.getAppAuthorizeSetting(),
			...uni.getAppBaseInfo()
		}
		// #endif

		// 从系统信息中提取窗口宽度和高度，包装成对象返回
		return {
			w: info.windowWidth,  // 窗口宽度（不包含导航栏、状态栏等）
			h: info.windowHeight  // 窗口高度（不包含导航栏、状态栏等）
		};
	},
	// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸，为支付宝的bug(2020-07-21)
	// 解决办法为在组件根部再套一个没有任何作用的view元素
	$uGetRect(selector : any, all : any) {
		return new Promise((resolve) => {
			// #ifndef APP-NVUE

			const instance : any = getCurrentInstance();
			uni.createSelectorQuery().in(instance.proxy)[all ? 'selectAll' : 'select'](selector)
				.boundingClientRect((rect) => {
					if (all && Array.isArray(rect) && rect.length) {
						resolve(rect)
					}
					if (!all && rect) {
						resolve(rect)
					}
				})
				.exec()
			// #endif


		})
	},
	/**
	 * 获取父组件实例
	 * @param name 父组件的name属性
	 * @returns 父组件实例或false
	 */
	$parent(targetName ?: string) : ComponentInternalInstance | false {
		let instance = getCurrentInstance()

		if (instance) {
			let parent = instance.parent;
			while (parent) {
				if (
					targetName &&
					parent.type &&
					(parent.type as any).__name !== targetName
				) {
					// 继续向上找
					parent = parent.parent
				} else {
					return parent
				}
			}

			return false
		} else {
			return false
		}
	},

	/**
	 * 处理页面底部安全距离样式
	 * 用于适配不同设备（尤其是全面屏手机）的底部安全区域，避免内容被底部导航栏遮挡
	 * @returns 包含底部内边距样式的字符串，或undefined（无需额外安全距离时）
	 */
	disposeSafeBottomFun() : string | undefined {
		// 从本地存储中获取预先保存的底部安全区域高度
		// safeAreaBottom 通常是设备底部安全距离（如iPhone的底部横条区域高度）
		const safeAreaBottom = uni.getStorageSync('safeAreaBottom');

		// 判断是否存在有效的底部安全区域高度（大于0）
		if (safeAreaBottom > 0) {
			// 若存在，返回包含底部内边距的样式字符串
			// 安全区域高度 + 10rpx额外间距，确保内容完全显示在安全区域内
			return 'padding-bottom:' + (Number(safeAreaBottom) + 10) + 'rpx;';
		}

		// 若不存在有效的底部安全区域高度，返回undefined（不添加额外样式）
		return undefined;
	},

	/**
	 * 转换阿里巴巴图标相关的Unicode格式字符串为实际可显示的字符
	 * 支持两种常见Unicode表示格式的转换
	 * @param str - 需要转换的Unicode格式字符串
	 * @returns 转换后的实际字符，如果无法转换则返回原始字符串
	 */
	toUniCode(str : string) : string {
		// 处理以"\u"开头的Unicode格式（如"\ue600"）
		if (/^\\u/.test(str)) {
			// 通过JSON.parse将Unicode转义序列转换为实际字符
			// 替换处理是为了确保JSON解析时能正确识别转义序列
			return JSON.parse('"' + str.replace(/\\u/g, '\\u') + '"');
		}
		// 处理以"&#x"开头的HTML实体Unicode格式（如"&#xe600;"）
		else if (/^&#x/.test(str)) {
			// 使用正则匹配提取十六进制数字部分
			const match = str.match(/^&#x([0-9a-fA-F]+);$/);
			if (match) {
				// 将十六进制字符串转换为十进制码点
				const codePoint = parseInt(match[1], 16);
				// 根据码点生成对应的字符
				return String.fromCodePoint(codePoint);
			}
			// 若匹配失败，返回原始字符串
			return str;
		}
		// 既不是"\u"格式也不是"&#x"格式，直接返回原始字符串
		else {
			return str;
		}
	},
	/**
	 * 获取指定范围日期时间
	 * @param {number} n 指定天数 （例： n=1 一天后 ； n=-1 一天前）
	 * @return {string} 返回年月日格式数据
	 */
	getDateAfterNDays(n : number) : string {
		// 获取当前日期  
		const now = new Date();

		// 创建一个新的Date对象，表示n天后的日期  
		// 时间戳是以毫秒为单位的，所以我们需要将天数转换为毫秒（1天 = 24小时 * 60分钟 * 60秒 * 1000毫秒）  
		const futureDate = new Date(now.getTime() + n * 24 * 60 * 60 * 1000);

		// 返回格式化后的日期字符串（可选）  
		// 这里使用了简单的格式化，你可以根据需要调整  
		const formattedDate = futureDate.toISOString().split('T')[0];

		return formattedDate;
	},
	/**
	 * 功能：路由解析并且拼接
	 * 使用：parseUrl('home', {id:1,type: 'add'})
	 * 得到：'/pages/home?id=1&type=add'
	 */
	parseUrl(url : string, params ?: Record<string, any>) : string {
		if (!params) return `/pages/${url}`;

		let arr : string[] = [];
		for (const i in params) {
			arr.push(i + "=" + params[i]);
		}

		let string = "/pages/" + url;
		if (arr.length > 0) {
			string += "?" + arr.join("&");
		}

		return string;
	},
	/**
	 * @description 添加单位，如果有rpx，upx，%，px等单位结尾或者值为auto，直接返回，否则加上px单位结尾
	 * @param {string|number} value 需要添加单位的值
	 * @param {string} unit 添加的单位名 比如px
	 */
	addUnit(value : string | number = 'auto', unit : string = (uni as any)?.$u?.config?.unit ?? 'px') : string {
		value = String(value);
		// 用uView内置验证规则中的number判断是否为数值
		return test.number(value) ? `${value}${unit}` : value;
	},
	/**
	 * @description 深度克隆
	 * @param {any} obj 需要深度克隆的对象
	 * @param cache 缓存
	 * @returns {*} 克隆后的对象或者原值（不是对象）
	 */
	deepClone<T>(obj : T, cache : WeakMap<object, any> = new WeakMap()) : T {
		if (obj === null || typeof obj !== 'object') return obj;
		if (cache.has(obj as object)) return cache.get(obj as object);
		let clone : any;

		if (obj instanceof Date) {
			clone = new Date((obj as Date).getTime());
		} else if (obj instanceof RegExp) {
			clone = new RegExp(obj);
		} else if (obj instanceof Map) {
			clone = new Map(Array.from(obj, ([key, value]) => [key, Utils.deepClone(value, cache)]));
		} else if (obj instanceof Set) {
			clone = new Set(Array.from(obj, value => Utils.deepClone(value, cache)));
		} else if (Array.isArray(obj)) {
			clone = (obj as any[]).map(value => Utils.deepClone(value, cache));
		} else if (Object.prototype.toString.call(obj) === '[object Object]') {
			clone = Object.create(Object.getPrototypeOf(obj));
			cache.set(obj as object, clone);
			for (const [key, value] of Object.entries(obj)) {
				clone[key] = Utils.deepClone(value, cache);
			}
		} else {
			clone = Object.assign({}, obj);
		}
		cache.set(obj as object, clone);
		return clone as T;
	},
	/**
	 * @description JS对象深度合并
	 * @param {object} target 需要拷贝的对象
	 * @param {object} source 拷贝的来源对象
	 * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）
	 */
	deepMerge<T extends object>(target : T = {} as T, source : Partial<T> = {} as Partial<T>) : T | boolean {
		const clonedTarget = Utils.deepClone(target);
		if (typeof clonedTarget !== 'object' || clonedTarget === null || typeof source !== 'object' || source === null) {
			return clonedTarget as T;
		}

		const merged : any = Array.isArray(clonedTarget) ? clonedTarget.slice() : Object.assign({}, clonedTarget);

		for (const prop in source) {
			if (!source.hasOwnProperty(prop)) continue;
			// @ts-ignore
			const sourceValue = source[prop];
			// @ts-ignore
			const targetValue = merged[prop];

			if (sourceValue instanceof Date) {
				merged[prop] = new Date(sourceValue);
			} else if (sourceValue instanceof RegExp) {
				merged[prop] = new RegExp(sourceValue);
			} else if (sourceValue instanceof Map) {
				merged[prop] = new Map(sourceValue);
			} else if (sourceValue instanceof Set) {
				merged[prop] = new Set(sourceValue);
			} else if (typeof sourceValue === 'object' && sourceValue !== null) {
				merged[prop] = Utils.deepMerge(targetValue, sourceValue);
			} else {
				merged[prop] = sourceValue;
			}
		}

		return merged as T;
	},
	/**
	 * @description 判断开始时间是否大于结束时间
	 * @param {string} startDateStr 开始时间
	 * @param {string} endDateStr 	结束时间
	 * @returns {boolean} 	返回是否大于
	 */
	isValidDateRange(startDateStr : string, endDateStr : string) : boolean {
		// 使用 Date.parse() 尝试解析日期字符串  
		// 注意：Date.parse() 对于非标准格式的日期字符串可能不会正确工作  
		// 但对于 "YYYY-MM-DD" 格式，它通常可以正常工作  
		const startDate = new Date(startDateStr);
		const endDate = new Date(endDateStr);

		// 检查两个日期是否都是有效的  
		if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
			return false; // 至少有一个日期是无效的  
		}

		// 检查开始时间是否不大于结束时间  
		return startDate.getTime() <= endDate.getTime();
	},
	/** 比较版本号 
	 * 返回值： 0 高于服务器版本； 1 与服务器版本一致； 2 低于服务器版本；
	 * @param {string} OldV 从服务器获取的版本号 
	 * @param {string} NewV 从本机获取的版本号 
	 */
	versionFn(NewV : string, OldV : string) : 0 | 1 | 2 {
		let curArr = NewV.split('.');
		let reqArr = OldV.split('.');
		const maxL = Math.max(curArr.length, reqArr.length);

		// 使用 padStart 来确保两个数组长度相同，不足部分用 '0' 填充  
		curArr = curArr.map((v, i) => v.padStart(maxL - i, '0')).slice(0, maxL);
		reqArr = reqArr.map((v, i) => v.padStart(maxL - i, '0')).slice(0, maxL);

		for (let index = 0; index < maxL; index++) {
			const diff = parseInt(curArr[index], 10) - parseInt(reqArr[index], 10);
			if (diff !== 0) {
				return diff > 0 ? 2 : 0; // 直接返回 2 或 0，无需再检查相等的情况  
			}
		}

		// 如果所有位都相等，则返回 1  
		return 1;
	},
	/**
	 * 判断传入的值受否为空
	 * @param  {any}  value  传入的值
	 * @param  {Boolean}  flash  是否开启 0 判定为空
	 * @return {boolean} 返回布尔值 true | false
	 */
	isEmpty(value : any, flash ?: Boolean) : boolean {
		if (flash && value === 0) {
			return true;
		}
		if (typeof value === 'undefined' || value === undefined || value === null || value === 'null') {
			return true;
		}

		if (typeof value === 'string' && value.trim() === '') {
			return true;
		}

		if (Array.isArray(value) && value.length === 0) {
			return true;
		}

		if (typeof value === 'object' && value !== null && Object.keys(value).length === 0) {
			return true;
		}

		return false;
	},
	/** 判断数组中某字段是否都相同
	 * @param {Object[]} arr 数组
	 * @param {string} propertyName 属性名
	 */
	arePropertiesConsistent(arr : object[], propertyName : string) : boolean {
		// 验证输入  
		if (!Array.isArray(arr) || typeof propertyName !== 'string' || arr.length === 0) {
			return false; // 输入无效或数组为空  
		}

		// 假设第一个元素存在且其属性存在（在调用前进行验证以避免运行时错误）  
		// @ts-ignore
		const firstValue = arr[0][propertyName];
		if (firstValue === undefined) {
			return true; // 如果第一个元素的属性不存在，认为所有元素该属性都不存在，视为“一致”  
			// 或者可以改为抛出错误，取决于你的需求  
		}

		// 遍历数组，比较属性值  
		for (let i = 1; i < arr.length; i++) {
			// @ts-ignore
			if (arr[i][propertyName] !== firstValue) {
				return false; // 属性值不一致，立即返回  
			}
		}

		// 所有属性值都一致  
		return true;
	},
	/**
	 * 根据给定的日期格式字符串判断其是日期格式还是日期时间格式。
	 *
	 * @param {string} format - 日期格式字符串，可能包含日期和时间部分。
	 * @returns {string} - 如果字符串包含时间部分（hh:mm 或 hh:mm:ss），则返回 'datetime'；否则返回 'date'。
	 */
	getDateFormatType(format : string) : 'date' | 'datetime' {
		const hasTime = /\bhh:mm(?::ss)?\b/.test(format);
		return hasTime ? 'datetime' : 'date';
	},
	/**
	 * 格式化日期时间
	 * @param {string | number | Date} date 传入时间（支持字符串、时间戳、Date对象）
	 * @param {string} fmt 需要的格式（如：'Y-MM-dd hh:mm:ss'）
	 * @return {string} 返回对应格式的日期字符串
	 */
	dateFormat(date : string | number | Date, fmt : string) : string {
		// console.log(date);
		// 统一转换为Date对象，处理不同类型的输入
		let dateObj : Date;
		if (date instanceof Date) {
			dateObj = date;
		} else if (typeof date === 'number') {
			// 处理时间戳（支持秒级和毫秒级）
			dateObj = new Date(date.toString().length === 10 ? date * 1000 : date);
		} else {
			// 处理字符串类型
			dateObj = new Date(date);
		}

		// 验证日期有效性
		if (isNaN(dateObj.getTime())) {
			console.warn(`Invalid date: ${date}`);
			return '';
		}

		const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];

		// 日期格式化映射表，明确每个格式对应的取值逻辑
		const formatMap : Record<string, number | string> = {
			'Y': dateObj.getFullYear(),
			'M+': dateObj.getMonth() + 1, // 月份（1-12）
			'd+': dateObj.getDate(), // 日（1-31）
			'h+': dateObj.getHours(), // 小时（0-23）
			'm+': dateObj.getMinutes(), // 分（0-59）
			's+': dateObj.getSeconds(), // 秒（0-59）
			'q+': Math.floor((dateObj.getMonth() + 3) / 3), // 季度（1-4）
			'S': dateObj.getMilliseconds(), // 毫秒（0-999）
			'w': dateObj.getDay(), // 星期（0-6，0为周日）
			'W': weekdays[dateObj.getDay()], // 星期中文名称
			'T': 'T' // ISO格式分隔符
		};

		// 处理年份（支持不同长度的年份格式，如'y'->'5'，'yy'->'05'，'yyyy'->'2005'）
		const yearRegex = /(y+)/;
		if (yearRegex.test(fmt)) {
			const yearMatch = yearRegex.exec(fmt)![1]; // 使用exec获取匹配组
			const yearStr = dateObj.getFullYear().toString();
			fmt = fmt.replace(
				yearMatch,
				yearStr.substr(4 - yearMatch.length)
			);
		}

		// 处理其他格式符
		for (const key in formatMap) {
			if (Object.prototype.hasOwnProperty.call(formatMap, key)) {
				const reg = new RegExp(`(${key})`);
				const matchResult = reg.exec(fmt);
				if (matchResult) {
					const matchedPart = matchResult[1]; // 获取匹配到的部分（如'MM'）
					const value = formatMap[key];
					const valueStr = String(value);

					// 根据格式符长度决定是否补零（如'M'->'3'，'MM'->'03'）
					const replacement = matchedPart.length === 1
						? valueStr
						: (`00${valueStr}`).substr(valueStr.length);

					fmt = fmt.replace(reg, replacement);
				}
			}
		}

		return fmt;
	},
	/** isPda 判断当前设备型号是否属于pda， 
	 * @return {boolean}  返回一个布尔值
	 */
	isPda() : boolean {
		try {
			// 尝试从存储中获取系统设备信息  
			const sysDeviceInfo = JSON.parse(uni.getStorageSync('sysDeviceInfo'));
			// 检查设备模型是否为预定的PDA模型之一  
			if ((sysDeviceInfo as any).hasOwnProperty('scanDeviceType')) {
				return (sysDeviceInfo as any).scanDeviceType === 'PDA';
			} else {
				return false;
			}
		} catch (error) {
			return false;
		}
	},
	/**
	 * 清除指定对象内各字段的值，会直接修改传入的对象原数据
	 * @param {Object} cloneData 需要清空的对象
	 * @param {string[]} excludeArr 需要排除清除的数据
	 */
	clearFormData(cloneData : Record<string, any>, excludeArr : string[] = []) : void {
		Object.keys(cloneData).forEach(key => {
			if (!excludeArr.includes(key)) {
				// 检查cloneData[key]的类型，并相应地设置值  
				if (cloneData[key] === null) {
					// 如果为null，则保持为null  
					cloneData[key] = null;
				} else if (Array.isArray(cloneData[key])) {
					// 如果为数组，则清空数组  
					cloneData[key] = [];
				} else if (typeof cloneData[key] === 'object' && cloneData[key] !== null) {
					// 如果为对象（且不是null），则清空对象  
					cloneData[key] = {};
				} else if (typeof cloneData[key] === 'number') {
					// 如果为number，则设置为0  
					cloneData[key] = 0;
				} else {
					// 其他情况，设置为空字符串  
					cloneData[key] = '';
				}
			}
		});
	},
	// 缓存 - start
	/** App计算缓存
	 * @param {(size: string) => void} callback 
	 */
	formatSize(callback : (size : string) => void) : void {
		(uni as any).plus.cache.calculate(function (size : number) {
			let fileSizeString = "0B";
			const sizeCache = parseInt(size.toString());
			if (sizeCache == 0) {
				fileSizeString = "0B";
			} else if (sizeCache < 1024) {
				fileSizeString = sizeCache + "B";
			} else if (sizeCache < 1048576) {
				fileSizeString = (sizeCache / 1024).toFixed(2) + "KB";
			} else if (sizeCache < 1073741824) {
				fileSizeString = (sizeCache / 1048576).toFixed(2) + "MB";
			} else {
				fileSizeString = (sizeCache / 1073741824).toFixed(2) + "GB";
			}
			callback && callback(fileSizeString);
		});
	},
	/** *  App清理缓存
	 * @param {string} fileSizeString 当前缓存
	 * @param {() => void} callback 
	 * @returns {Promise<void>}
	 */
	clearCache(fileSizeString : string, callback ?: () => void) : Promise<void> {
		if (fileSizeString == '0B') {
			uni.showToast({
				title: '暂无缓存~',
				duration: 2000,
				icon: 'none'
			});
			return Promise.resolve();
		}

		return new Promise((resolve, reject) => {
			const os = (uni as any).plus.os.name;

			if (os == 'Android') {
				const main = (uni as any).plus.android.runtimeMainActivity();
				const sdRoot = main.getCacheDir();
				const files = (uni as any).plus.android.invoke(sdRoot, "listFiles");

				// 使用 async/await 来处理异步文件删除  
				(async function () {
					try {
						for (const file of files) {
							const filePath = '' + file; // 转换为字符串路径  
							await new Promise((resolveFile, rejectFile) => {
								(uni as any).plus.io.resolveLocalFileSystemURL(filePath, function (entry : any) {
									if (entry.isDirectory) {
										entry.removeRecursively(resolveFile, rejectFile);
									} else {
										entry.remove(resolveFile, rejectFile);
									}
								}, rejectFile);
							});
						}
						uni.showToast({
							title: '缓存清理完成',
							duration: 2000
						});
						resolve();
						if (callback && typeof callback === 'function') {
							callback();
						}
					} catch (error) {
						console.error('清理缓存时出错:', error);
						reject(error);
					}
				})();

			} else { // iOS  
				(uni as any).plus.cache.clear(function () {
					uni.showToast({
						title: '缓存清理完成',
						duration: 2000
					});
					resolve();
					if (callback && typeof callback === 'function') {
						callback();
					}
				}, reject);
			}
		});
	},

	/**
	 * 服务器资源文件映射
	 * @param {string} file 服务器文件地址
	 */
	getServerFile(file : string) : string {
		if (Utils.isEmpty(file)) "";

		let path = uni.getStorageSync("IPAgreement") + uni.getStorageSync("serverIP");

		return path + file;

	}






};